#!/usr/bin/env python

"""A weather chat bot.

Fetches weather information at the given place.
<p>
The weather information is fetched using the Yahoo! Weather and 
the Yahoo! GeoPlanet APIs.
"""

__author__ = 'Ajit Apte'

from HTMLParser import HTMLParser
import logging
import urllib

from django.utils import simplejson
from google.appengine.api import urlfetch
from google.appengine.api import xmpp
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

# Error messages
_ERROR = 'You are better off not knowing the weather here ;-)'
_INVALID_INPUT = 'Invalid Input'

# Weather related urls
_WEATHER_DETAIL_PREFIX = 'http://us.rd.yahoo.com/dailynews/rss/weather/'
_YAHOO_GEOPLANET_URL = 'http://where.yahooapis.com/v1/places.q(%s)?appid=V0X_OC_V34FS2gW_EJMoT6NwYnn7YFvFPwBZ_2uWA5cVeQzh_eUQCmyOR5omT0g-'
_YAHOO_WEATHER_URL = 'http://weather.yahooapis.com/forecastrss?w='

# Url shortening service
_URLY_API_URL = 'http://ur.ly/new.json?href=%s'
_URLY_PREFIX = 'http://ur.ly/'


class WeatherParser(HTMLParser):
	"""Parses out relevant weather info from the given weather feed."""
	def __init__(self, feed):
		HTMLParser.__init__(self)
		self._inside_link = False
		self._details_url = ''
		self._conditions = ''
		self._forecast = ''
		self.feed(feed)

	def handle_data(self, data):
		if self._inside_link and data.startswith(_WEATHER_DETAIL_PREFIX):
			# Shorten the URL using the ur.ly API
			urly = _URLY_API_URL.replace("%s", data)
			logging.debug('Url sent for shortening: %s' % urly)
			_json_feed = urlfetch.fetch(urly)
			logging.debug('Json feed as a string: %s' % _json_feed.content)
			self._details_url = _URLY_PREFIX + simplejson.loads(_json_feed.content)['code']
			# self._details_url = data

	def handle_endtag(self, tag):
		if tag == 'link':
			self._inside_link = False
			
	def handle_startendtag(self, tag, attrs):
		if tag == 'yweather:condition':
			attrs_dict = dict(attrs)
			self._conditions += attrs_dict['text'] + ', ' + attrs_dict['temp'] + 'F' 
		elif tag == 'yweather:forecast':
			attrs_dict = dict(attrs)
			self._forecast += '_' + attrs_dict['day'] + '_: ' + attrs_dict['text'] + ', ' + \
							  attrs_dict['low'] + 'F / ' + attrs_dict['high'] + 'F' + '\n' \
	
	def handle_starttag(self, tag, attrs):
		if tag == 'link':
			self._inside_link = True	
			
	def unknown_decl(self, decl):
		pass
	
	def get_weather(self):
		return '\n' + '*Current:* \n' + self._conditions  + '\n' + \
		       '*Forecast:* \n' + self._forecast + \
			   '*Full details:* ' + self._details_url
			   


class WoeidParser(HTMLParser):
	"""Parses out the WOEID from the given data feed."""
	def __init__(self, feed):
		HTMLParser.__init__(self)
		self._inside_woeid = False
		self._woeid = '0'
		self.feed(feed)

	def handle_data(self, data):
		if self._inside_woeid:
			self._woeid = data
			
	def handle_endtag(self, tag):
		if tag == 'woeid':
			self._inside_woeid = False
			
	def handle_starttag(self, tag, attrs):
		if tag == 'woeid':
			self._inside_woeid = True
	
	def get_woeid(self):
		return self._woeid


def _fetch_weather_for(woeid):
	"""Fetches weather data for the given woeid.
	
	Uses the Yahoo! Weather API. More details at:
	http://developer.yahoo.com/weather/
	"""
	url = _YAHOO_WEATHER_URL + woeid
	return urlfetch.fetch(url)

	
def _map_to_woeid(place):
	"""Maps the given place to its Where On Earth ID (WOEID).
	
	Uses the Yahoo! GeoPlanet API. More details at:
	http://developer.yahoo.com/geo/geoplanet/
	"""
	url = _YAHOO_GEOPLANET_URL.replace("%s", "'" + urllib.quote(place) + "'")
	result = urlfetch.fetch(url)
	if result == None:
		return None
	if result.status_code == 200:
		_parser = WoeidParser(result.content)
		woeid = _parser.get_woeid()
		return woeid
	else:
		return None

	
def _parse(weather):
	"""Parses the raw RSS weather feed into a human-readable string."""
	if _INVALID_INPUT in weather:
		return _INVALID_INPUT
	_parser = WeatherParser(weather)
	return _parser.get_weather()
	
	
class XMPPHandler(webapp.RequestHandler):
    def post(self):
        message = xmpp.Message(self.request.POST)
        if message.body[0:5].lower() == 'hello':
			message.reply(
				"Hi! I'm Climely. Gimme a place and I'll tell you about the weather there.")
        else:
			logging.debug('Place given by user: %s' % message.body)
			# Map given place to its Where On Earth ID (WOEID)
			place = message.body
			woeid = _map_to_woeid(place)
			if woeid == None:
				message.reply(_ERROR)
				return
			
			logging.debug('woeid for %s is %s' % (place, woeid))
			# Get weather conditions at that WOEID
			weather = _fetch_weather_for(woeid)
			if weather == None:
				message.reply(_ERROR)
				return

			logging.debug('Fetched weather info successfully')
			# Reply appropriately
			if weather.status_code == 200:
				logging.debug('Before parsing: %s' % weather.content)
				final_output = _parse(weather.content)
				logging.debug('The final output is: %s' % final_output)
				message.reply(final_output)
			else:
				message.reply(_ERROR)
			

# The Google AppEngine sends IM messages to this URL.
application = webapp.WSGIApplication([('/_ah/xmpp/message/chat/', XMPPHandler)],
                                     debug=True)

def main():
    run_wsgi_app(application)

if __name__ == "__main__":
    main()
